package com.googlecode.cswish.view;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.struts2.components.Component;
import org.htmlcleaner.HtmlCleaner;

import com.googlecode.cswish.Config;
import com.googlecode.cswish.model.TagScope;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.googlecode.cswish.util.FileUtil;
import com.googlecode.cswish.util.FrameConstant;
import com.googlecode.cswish.util.FreemarkerDStringTplEngine;
import com.googlecode.cswish.util.FreemarkerDStringTplEngine.StringTemplateBuilder;
import com.googlecode.cswish.util.FreemarkerDStringTplEngine.StringTemplateResource;
import com.googlecode.cswish.util.Functions;
import com.googlecode.cswish.util.TextUtil;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ValueStack;

import freemarker.core.Comment;
import freemarker.template.Template;

/**
 * User FreeMarker engineer to load a static page & use <@cs.mark/> replace all the extension
 * 
 * @author Jerry.Feng 2008-11-17
 */
public class Include extends Component {

	private static final Logger logger = Logger.getLogger(Include.class);

	public final static String INCLUDE_PAGE = "_include_page";
	
	// the current template page
	protected String curpage;
	
	// the included page
	protected String page;
	
	// the target element of the included page (optional)
	protected String name;
	
	protected boolean genDir;
	
	// TODO: purpose of 'preCompile = false'?
	protected boolean preCompile = true;
	
    private ObjectFactory objectFactory;
    	
	@Resource
	private Config config;

	@Resource
	private Functions functions;
    
	@Resource
    private FreemarkerDStringTplEngine freemarkerDStringTplEngine;

	private final static String BUILDER_NAME = "include";
	
    @Inject
    public void setObjectFactory(com.opensymphony.xwork2.ObjectFactory objectFactory) {
    	if (this.objectFactory == null) {
	    	this.objectFactory = ObjectFactory.getInstance();
	    	this.objectFactory.autoWireBean(this);
	    	if (!freemarkerDStringTplEngine.hasBuilder(BUILDER_NAME)) {
	    		freemarkerDStringTplEngine.registerBuilder(BUILDER_NAME, new IncludeStringTemplateBuilder());
	    	}
    	}
    }

	class IncludeStringTemplateBuilder implements StringTemplateBuilder {

		@Override
		public StringTemplateResource create(String name) {
			String[] keys = StringUtils.split(name, '\n');
			boolean preCompile = new Boolean(keys[0]);
			String page1 = keys[1];		// the include page
			String page2 = keys[2];		// current page
			String targetElementName = keys[3];
			if ("null".equals(targetElementName)) {
				targetElementName = null;
			}

			String content = null;
			File file1 = null;
			File file2 = null;
			if (preCompile) {
				ValueStack stack = ActionContext.getContext().getValueStack();
				// load all the extend point
				List<String> extendPoints = Mark.getExtensionNames(stack, page1);

				// load html, use <@s.mark/> to replace these extend point
				String webRootPath = config.getRealWebRootPath();
				String fullPath = webRootPath + page1;
				try {
					// ensure the page has been generated
					if (page1.startsWith(config.getViewDir4GenCode())) {
						int suffixIndex = page1.lastIndexOf('.');
						String url = page1.substring(config.getViewDir4GenCode().length(), suffixIndex);
						functions.hasPage(url);
					}

					file1 = new File(fullPath);
					if (page2 != null) {
						file2 = new File(webRootPath + page2);
					}
					if (!file1.exists()) {
						fullPath = webRootPath + config.getViewDir4GenCode() + page1;
						file1 = new File(fullPath);
					}
					content = FileUtils.readFileToString(file1, "UTF-8");

					if (targetElementName != null) {
						content = "<#include '/pages/taglib/core.ftl'>\n<#include '/pages/taglib/common.ftl'>\n<#include '/pages/taglib/container.ftl'>\n" + TextUtil.get(content, targetElementName);
					}
				} catch (IOException ex) {
					content = "";
					logger.error("Falied to read file " + fullPath, ex);
				}

				for (String target : extendPoints) {
					char c = target.charAt(target.length() - 1);

					Object obj = Mark.getExtension(stack, target);
					String newContent;
					if (obj instanceof Comment) {
						Comment comment = (Comment) obj;
						newContent = comment.getText();
					} else {
						newContent = "<@cs.mark name='" + Mark.getExtensionName(target) + "'/>";
					}

					TagScope tagScope = TagScope.valueOf(c);
					boolean onlyChild = tagScope == TagScope.INNER;
					String identity = onlyChild ? target : target.substring(0, target.length() - 1);
					content = TextUtil.replace(content, identity, newContent, onlyChild);
				}

			} else {
				InputStream input = null;
				try {
					input = this.getClass().getResourceAsStream(page1);
					content = FileUtil.readFileToString(input, HtmlCleaner.DEFAULT_CHARSET);
				} finally {
					IOUtils.closeQuietly(input);
				}

				if (logger.isDebugEnabled()) {
					logger.debug("Include page: " + page1);
				}
			}

			StringTemplateResource source = new StringTemplateResource();
			source.setName(name);
			source.setContent(content);
			if (file1 != null) {
				source.setSource(new File[] {file1, file2});
				long lastModified = file1.lastModified();
				long lastModified2 = file2 == null ? 0 : file2.lastModified();
				if (lastModified2 > lastModified) {
					lastModified = lastModified2;
				}
				source.setLastCheckTime(lastModified);
			}
			return source;
		}
		
	}
	
    public String getCurpage() {
		return curpage;
	}

	public void setCurpage(String curpage) {
		this.curpage = curpage;
	}

	public String getPage() {
		return page;
	}
    
	public void setPage(String page) {
		this.page = page;
	}
	
	public boolean isGenDir() {
		return genDir;
	}

	public void setGenDir(boolean genDir) {
		this.genDir = genDir;
	}

	public boolean isPreCompile() {
		return preCompile;
	}

	public void setPreCompile(boolean preCompile) {
		this.preCompile = preCompile;
	}

	public Include(ValueStack stack) {
		super(stack);
	}
	
	@SuppressWarnings("unchecked")
	public boolean start(Writer writer) {
		if (isGenDir()) {
			this.page = config.getSitePath() + config.getViewDir4GenCode() + this.page;
		} else {
			this.page = config.getViewDir() + this.page;
		}
		stack.getContext().put(INCLUDE_PAGE, this.page);
		
        return super.start(writer);
    }

	@SuppressWarnings("unchecked")
	public boolean end(Writer writer, String body) {
		stack.getContext().put(INCLUDE_PAGE, null);
		
		// hardcode logic for the Call.java
		if (name == null) {
			name = super.stack.findString(FrameConstant.PARAM_ELEMENT_NAME);
		}
		
		try {
			String key = "" + preCompile + '\n' + page + '\n' + curpage + '\n' + name;
			// see StringTemplateLoader.getLastModified, findTemplateSource
			Template template = freemarkerDStringTplEngine.getTemplate(BUILDER_NAME, key);
			freemarkerDStringTplEngine.renderTemplate(writer, getStack(), template);
		} catch (Exception e) {
			logger.error("error when rendering", e);
        }
		
		popComponentStack();
		return false;
	}	
}